/**
 * @file unistd.c
 * @brief Äquivalenzbibliothek für PTK
 *
 * @author Michael Kammer
 * @author Julius Werner
 * @date 15.08.2011
*/
#include "crt0.h"
#include "errno.h"
#include "ptchannel.h"
#include "stdarg.h"
#include "stdbool.h"
#include "stdint.h"
#include "string.h"
#include "sys/stat_pt.h"
#include "unistd_pt.h"

#include "ptk_channel.h"
#include "ptk_fs.h"
#include "ptk_types.h"

pid_t pt_fork()
{
	struct syscall_t {
		ptk_syscall_callstruct call;
		ptkp_kmigrated_syscall_data data;
	} syscall;

	ptk_channel_packetheader packet;
	pid_t my_pid = pt_getpid();
	unsigned int channelid = pt_ccreate(); /* neuer Kanal für Antwort */

	syscall.call.opcode      = soc_fork;
	syscall.call.tag         = 0;
	syscall.call.pairchannel = channelid;
	syscall.call.payload_length = sizeof(struct syscall_t) -
	  sizeof(ptk_syscall_callstruct);

	syscall.data.pid = my_pid;
	syscall.data.migrate_to = 0;

	pt_cwrite(PTKP_KMIGRATED_CHANNEL, cpt_syscall, &syscall,
	  sizeof(struct syscall_t));
	pt_yield();
	  
	while (1)
	{
		pt_cnext(channelid, &packet);

		if (packet.type == cpt_null) continue; /* Weiterwarten */

		/* Konvention: Antwortpacket ist IMMER an Elternprozess adressiert */
		if ( (packet.type != cpt_syscall_ret) ||
		  (packet.length != sizeof(struct syscall_t)) ||
		  (packet.sender != my_pid) )
		{
			/* Nicht das, worauf wir warten */
			pt_cdrop(channelid);
			continue;
		}

		pt_cread(channelid, &packet, &syscall, sizeof(struct syscall_t));
		break;
	}

	pt_cclose(channelid);
	
	if (syscall.data.pid == 0)
	{
		/* Fehler */
		return -1;
	}
	
	if (my_pid != pt_getpid())
	{
		/* geänderte Prozess-ID: Kindprozess */
		init_fs(); // Dateideskriptor-Kanäle erneut öffnen
		return 0;
	} else {
		/* gleiche Prozess-ID:   Elternprozess */
		return syscall.data.pid;
	}
}

unsigned int pt_sleep(unsigned int seconds)
{
	long uptime;
	struct sysinfo info;

	sysinfo(&info);
	uptime = info.uptime;

	while (1)
	{
		sysinfo(&info);
		if ( (info.uptime - uptime) >= seconds ) return 0;
		pt_yield();
	}
}

#define FD_PER_PROCESS 32
#define FS_RETRY_TIMES 5
#define FS_REQUEST_TIMEOUT 2

// TODO: find solution for this
#define stdin_channel 0
#define stdout_channel 1
#define stderr_channel 2

typedef enum __attribute__((__packed__)) {
	FD_UNALLOC,
	FD_FILE,
	FD_CHANNEL
} fd_type_t;

typedef struct {
	fd_type_t type;
	open_flags_t oflag;
	uint16_t reserved; // padding
	int global_id;
} fd_t;

static fd_t fd_table[FD_PER_PROCESS];
static int free_fd;
static int retchan;
static uint8_t req_id;
static char workdir[MAX_PATHLENGTH];

static inline int get_free_fd() {
	int tmp = free_fd++;
	while (free_fd < FD_PER_PROCESS && fd_table[free_fd].type != FD_UNALLOC) free_fd++;
	return tmp;
}

static bool fs_request(fs_msg_pointer_t req, size_t reqsize, fs_ret_t* ret, size_t retsize) {
	req.__m->req_id = ++req_id;
	for (int i = 0; i < FS_RETRY_TIMES; i++) {
		pt_cwrite(FS_CHAN, cpt_stream, (void*)req.__m, reqsize);
		struct sysinfo info;
		sysinfo(&info);
		long start = info.uptime;
		do {
			if (pt_cread(retchan, 0, ret, retsize) && ret->req_id == req_id) return true;
			pt_yield();
			sysinfo(&info);
		} while (info.uptime - start <= FS_REQUEST_TIMEOUT);
	}
	errno = EIO;
	return false;
}

static ssize_t canonicalize(char* buffer, const char* path) {
	char* dst = buffer;
	const char* src = path;
	if (src[0] != '/') {
		strncpy(buffer, workdir, MAX_PATHLENGTH);
		dst = buffer + strlen(buffer);
		src--;
	}
	do {
		src++;
		if (src - path < MAX_PATHLENGTH - 1 && src[0] == '.' && (src[1] == '/' || src[1] == '\0')) {
			src++;
		} else if (src - path < MAX_PATHLENGTH - 2 && src[0] == '.' && src[1] == '.' && (src[2] == '/' || src[2] == '\0')) {
			if (dst > buffer) do dst--; while (dst > buffer && dst[0] != '/');
			src += 2;
		} else if (src[0] != '/') {
			if (src[0]) *dst++ = '/';
			while (src - path < MAX_PATHLENGTH && dst - buffer < MAX_PATHLENGTH && src[0] && src[0] != '/') *dst++ = *src++;
		}
		if (src - path >= MAX_PATHLENGTH || dst - buffer >= MAX_PATHLENGTH) {
			errno = ENAMETOOLONG;
			return -1;
		}
	} while (src[0] == '/');
	*dst++ = 0;
	return dst - buffer;
}

/**
 * @brief
 *   Initialisiert libC file system wrapper... muss bei
 *   jedem Prozessstart einmal aufgerufen werden.
 */
void init_fs() {
	memset(fd_table, 0, sizeof(fd_table));
	// TODO: global_id von stdin/stdout/stderr setzen
	fd_table[0].type = FD_CHANNEL;
	fd_table[0].oflag = O_RDONLY;
	fd_table[1].type = FD_CHANNEL;
	fd_table[1].oflag = O_WRONLY;
	fd_table[2].type = FD_CHANNEL;
	fd_table[2].oflag = O_WRONLY;
	free_fd = 3;
	retchan = pt_ccreate();
	req_id = 0;
	workdir[0] = '\0'; // root Verzeichnis
}

int pt_isatty(int fd) {
	if ((unsigned)fd >= FD_PER_PROCESS || fd_table[fd].type == FD_UNALLOC) {
		errno = EBADF;
		return 0;
	}
	errno = ENOTTY;
	return 0;
}

int copen(int id, int oflag) {
	if (free_fd == FD_PER_PROCESS) {
		errno = EMFILE;
		return -1;
	}
	int fd = get_free_fd();
	if (oflag & O_RDONLY && !pt_copen(id)) {
		errno = EIO;
		return -1;
	}
	fd_table[fd].type = FD_CHANNEL;
	fd_table[fd].global_id = id;
	fd_table[fd].oflag = (uint8_t)(oflag & 0xFF);
	return fd;
}

int pt_open(const char* path, int oflag, ...) {
	if (oflag & O_WRONLY) {
		errno = EACCES;
		return -1;
	}
	if (free_fd == FD_PER_PROCESS) {
		errno = EMFILE;
		return -1;
	}
	fs_msg_open_t* msg = alloca(MAX_FS_MSG_SIZE);
	msg->type = FS_MSG_OPEN;
	msg->fd = 0;
	msg->retchan = retchan;
	if (canonicalize(msg->name, path) == -1) return -1;
	fs_ret_t ret;
	if (!fs_request(msg, MAX_FS_MSG_SIZE, &ret, sizeof(ret))) return -1;
	if (ret.status < 0) {
		errno = -ret.status;
		return -1;
	}
	int fd = get_free_fd();
	fd_table[fd].type = FD_FILE;
	fd_table[fd].global_id = ret.status;
	fd_table[fd].oflag = (uint8_t)(oflag & 0xFF);
	return fd;
}

ssize_t pt_read(int fd, void* buf, size_t len) {
	if ((unsigned)fd >= FD_PER_PROCESS) {
		errno = EBADF;
		return -1;
	}
	switch (fd_table[fd].type) {
		case FD_UNALLOC:
			errno = EBADF;
			return -1;
		case FD_CHANNEL:;
			pt_cpheader header;
			if (fd_table[fd].oflag & O_NONBLOCK) {
				if (pt_cread(fd_table[fd].global_id, &header, buf, len)) {
					return header.length;
				} else {
					errno = EAGAIN;
					return -1;
				}
			} else {
				while (!pt_cread(fd_table[fd].global_id, &header, buf, len)) pt_yield();
				return header.length;
			}
		case FD_FILE:;
			size_t bytes = 0;
			fs_msg_read_t msg = {FS_MSG_READ, 0, fd_table[fd].global_id, retchan, FS_MAX_DATA};
			fs_ret_t* ret = alloca(sizeof(fs_ret_t) + min(len, FS_MAX_DATA));
			for (; len > 0; len -= FS_MAX_DATA) {
				if (len < FS_MAX_DATA) msg.size = len;
				if (!fs_request(&msg, sizeof(msg), ret, sizeof(fs_ret_t) + msg.size)) return -1;
				if (ret->status < 0) {
					errno = -ret->status;
					return -1;
				} else if (ret->status == 0) break;
				memcpy(buf + bytes, ret->data, ret->status);
				bytes += ret->status;
			}
			return bytes;
		default:
			errno = EBADF;
			return -1;
	}
}

ssize_t pt_write(int fd, void* buf, size_t len) {
	if ((unsigned)fd >= FD_PER_PROCESS) {
		errno = EBADF;
		return -1;
	}
	switch (fd_table[fd].type) {
		case FD_UNALLOC:
			errno = EBADF;
			return -1;
		case FD_CHANNEL:
			pt_cwrite(fd_table[fd].global_id, cpt_stream, buf, len);
			return len;
		case FD_FILE:
			errno = EBADF; // file descriptor is "bad" because it cannot possibly have the "w" mode
			return -1;
		default:
			errno = EBADF;
			return -1;
	}
}

ssize_t pt_lseek(int fd, size_t offset, int whence) {
	if (whence == SEEK_END) {
		errno = EINVAL;
		return -1;
	}
	if ((unsigned)fd >= FD_PER_PROCESS) {
		errno = EBADF;
		return -1;
	}
	switch (fd_table[fd].type) {
		case FD_UNALLOC:
			errno = EBADF;
			return -1;
		case FD_CHANNEL:	
			errno = ESPIPE;
			return -1;
		case FD_FILE:;
			fs_msg_seek_t msg = {(whence == SEEK_CUR ? FS_MSG_SEEK_CUR : FS_MSG_SEEK_SET), 0, fd_table[fd].global_id, retchan, offset};
			fs_ret_t ret;
			if (!fs_request(&msg, sizeof(msg), &ret, sizeof(ret))) return -1;
			if (ret.status < 0) {
				errno = -ret.status;
				return -1;
			}
			return ret.status;
		default:
			errno = EBADF;
			return -1;
	}
}

int pt_close(int fd) {
	if ((unsigned)fd >= FD_PER_PROCESS) {
		errno = EBADF;
		return -1;
	}
	switch (fd_table[fd].type) {
		case FD_UNALLOC:
			errno = EBADF;
			return -1;
		case FD_CHANNEL:
			if (fd_table[fd].oflag & O_RDONLY) pt_cclose(fd_table[fd].global_id);
			fd_table[fd].type = FD_UNALLOC;
			if (fd < free_fd) free_fd = fd;
			return 0;
		case FD_FILE:;
			fs_msg_t msg = {FS_MSG_CLOSE, 0, fd_table[fd].global_id, retchan};
			fs_ret_t ret;
			if (!fs_request(&msg, sizeof(msg), &ret, sizeof(ret))) return -1;
			if (ret.status < 0) {
				errno = -ret.status;
				return -1;
			}
			fd_table[fd].type = FD_UNALLOC;
			if (fd < free_fd) free_fd = fd;
			return ret.status;
		default:
			errno = EBADF;
			return -1;
	}
}

int fstat(int fd, struct stat* buf) {
	if ((unsigned)fd >= FD_PER_PROCESS) {
		errno = EBADF;
		return -1;
	}
	switch (fd_table[fd].type) {
		case FD_UNALLOC:
			errno = EBADF;
			return -1;
		case FD_CHANNEL:
			errno = ESPIPE;
			return -1;
		case FD_FILE:;
			fs_msg_t msg = {FS_MSG_STAT, 0, fd_table[fd].global_id, retchan};
			fs_ret_t* ret = alloca(sizeof(fs_ret_t) + sizeof(struct stat));
			if (!fs_request(&msg, sizeof(msg), ret, sizeof(fs_ret_t) + sizeof(struct stat))) return -1;
			if (ret->status < 0) {
				errno = -ret->status;
				return -1;
			}
			memcpy(buf, ret->data, sizeof(struct stat));
			return ret->status;
		default:
			errno = EBADF;
			return -1;
	}
}

int pt_stat(const char* path, struct stat* buf) {
	int fd = open(path, 0, 0);
	if (fd == -1) return -1;
	int result = fstat(fd, buf);
	if (pt_close(fd) == -1 || result == -1) return -1;
	return result;
}

int link(const char *old, const char *new) {
	struct stat dummy;
	if (stat(old, &dummy) == -1) return -1; // for correct ENOENT
	if (stat(new, &dummy) != -1) {
		errno = EEXIST;
		return -1;
	} else if (errno != ENOENT) return -1;
	errno = EACCES; // every dir is read-only
	return -1;
}

int unlink(const char* name) {
	struct stat dummy;
	if (stat(name, &dummy) == -1) return -1;
	errno = EACCES;
	return -1;
}

int pipe(int filedes[2]) {
	int chan = pt_ccreate();
	if ((filedes[0] = copen(chan, O_RDONLY)) == -1) {
		pt_cclose(chan);
		return -1; // errno was set in copen
	}
	if ((filedes[1] = copen(chan, O_WRONLY)) == -1) {
		pt_close(filedes[0]);
		return -1; // errno was set in copen
	}
	return 0;
}

int fcntl(int fd, fcntl_cmd_t cmd, ...) {
	va_list args;
	va_start(args, cmd);
	if ((unsigned)fd >= FD_PER_PROCESS || fd_table[fd].type == FD_UNALLOC) {
		errno = EBADF;
		return -1;
	}
	switch (cmd) {
		case F_DUPFD:;
			int new_fd = va_arg(args, int);
			while (new_fd < FD_PER_PROCESS && fd_table[new_fd].type != FD_UNALLOC) new_fd++;
			return dup2(fd, new_fd);
		case F_GETFD:
			return (fd_table[fd].oflag & O_CLOEXEC ? true : false);
		case F_SETFD:;
			int fdflag = va_arg(args, int);
			if (fdflag & 1) fd_table[fd].oflag |= O_CLOEXEC;
			else fd_table[fd].oflag &= ~O_CLOEXEC;
			return 0;
		case F_GETFL:
			return fd_table[fd].oflag & O_NONBLOCK;
		case F_SETFL:;
			int flflag = va_arg(args, int);
			if (flflag & O_NONBLOCK) fd_table[fd].oflag |= O_NONBLOCK;
			else fd_table[fd].oflag &= ~O_NONBLOCK;
			return 0;
		default:
			errno = EINVAL;
			return -1;
	}
}

int dup2(int fd, int new_fd) {
	if ((unsigned)fd >= FD_PER_PROCESS || fd_table[fd].type == FD_UNALLOC) {
		errno = EBADF;
		return -1;
	}
	if ((unsigned)new_fd >= FD_PER_PROCESS) {
		errno = EMFILE;
		return -1;
	}
	if (fd_table[new_fd].type != FD_UNALLOC) {
		int result = pt_close(new_fd);
		if (result == -1) return -1; // errno is set
	}
	memcpy(&fd_table[new_fd], &fd_table[fd], sizeof(fd_t));
	fd_table[new_fd].oflag &= ~O_CLOEXEC;
	if (free_fd == new_fd) get_free_fd();
	return new_fd;
}

int chdir(const char* path) {
	char canon[MAX_PATHLENGTH];
	if (canonicalize(canon, path) == -1) return -1; // errno is set
	struct stat dummy;
	if (stat(canon, &dummy) == -1) return -1; // errno is set
	if (!(dummy.st_mode & S_IFDIR)) {
		errno = ENOTDIR;
		return -1;
	}
	strcpy(workdir, canon);
	return 0;
}

char* getcwd(char* buf, size_t size) {
	if (workdir[0]) strncpy(buf, workdir, size);
	else strncpy(buf, "/", size);
	if (buf[size-1]) {
		errno = ERANGE;
		return NULL;
	}
	return buf;
}

int closedir(DIR* dir) {
	int fd = dir->fd;
	free(dir);
	return pt_close(fd);
}

DIR* opendir(const char* path) {
	int fd = open(path, O_RDONLY);
	if (fd == -1) return 0; // errno is set
	DIR* dir = malloc(sizeof(DIR) + NAME_MAX);
	dir->fd = fd;
	return dir;
}

struct dirent* readdir(DIR* dir) {
	if ((unsigned)dir->fd >= FD_PER_PROCESS) {
		errno = EBADF;
		return 0;
	}
	if (fd_table[dir->fd].type != FD_FILE) {
		errno = EBADF;
		return 0;
	}
	fs_msg_read_t msg = {FS_MSG_READDIR, 0, fd_table[dir->fd].global_id, retchan, sizeof(struct dirent) + NAME_MAX};
	fs_ret_t* ret = alloca(sizeof(fs_ret_t) + msg.size);
	if (!fs_request(&msg, sizeof(msg), ret, sizeof(fs_ret_t) + msg.size)) return 0;
	if (ret->status < 0) {
		errno = -ret->status;
		return 0;
	};
	if (ret->status == 0) return 0;
	memcpy(&dir->buffer, ret->data, ret->status);
	return &dir->buffer;
}

int poll(struct pollfd fds[], nfds_t n, int timeout) {
	int crude_timeout = (timeout >> 8) + 1; // pretend that one pt_yield() equals roughly 128 ms
	while (true) {
		nfds_t selected = 0;
		for (struct pollfd* fd = fds; fd < fds + n; fd++) {
			if (fd->fd < 0) continue;
			fd->revents = 0;
			if (fd->fd > FD_PER_PROCESS || fd_table[fd->fd].type == FD_UNALLOC) {
				fd->revents = POLLINVAL;
			} else if (fd_table[fd->fd].type == FD_FILE) {
				if (fd->events & POLLIN) fd->revents |= POLLIN;
				if (fd->events & POLLRDNORM) fd->revents |= POLLRDNORM;
				if (fd->events & POLLRDBAND) fd->revents |= POLLRDBAND;
				if (fd->events & POLLPRI) fd->revents |= POLLPRI;
				if (fd->events & POLLOUT) fd->revents |= POLLOUT;
				if (fd->events & POLLWRBAND) fd->revents |= POLLWRBAND;
			} else if (fd_table[fd->fd].type == FD_CHANNEL) {
				pt_cpheader header;
				pt_cnext(fd_table[fd->fd].global_id, &header);
				if (header.type == cpt_stream) {
					if (fd->events & POLLIN) fd->revents |= POLLIN;
					if (fd->events & POLLRDNORM) fd->revents |= POLLRDNORM;
					if (fd->events & POLLRDBAND) fd->revents |= POLLRDBAND;
					if (fd->events & POLLPRI) fd->revents |= POLLPRI;
				}
				if (fd->events & POLLOUT) fd->revents |= POLLOUT;
				if (fd->events & POLLWRBAND) fd->revents |= POLLWRBAND;
			}	
			if (fd->revents) selected++;
		}
		if (selected) return selected;
		if (timeout == 0) return 0;
		if (timeout != -1 && !crude_timeout--) return 0;
		pt_yield();
	}
}

void _exit(int status) {
	fd_t* fd = __global_fs_state->fd_table;
	for (int i = 0; i < FD_PER_PROCESS; i++) if (fd[i].type != FD_UNALLOC) close(i); // ignore errors
	typedef void (*terminate_t) (int) __attribute__ ((noreturn));
	(*(terminate_t) PTK_PROCESS_TERM_ADDRESS)(status);
	while(1); // to skip warnings on noreturn
}

static int execfile(const char * path, char * const * argv, char * const * env) {
	if (!strcmp(path, BUSYBOX_MAGIC_PATH)) return exec(NULL, argv, env, __global_fs_state);
	int fd = open(path, O_RDONLY);
	if (fd == -1) return -1; // errno is set
	struct stat s;
	if (fstat(fd, &s) != 0) return -1; // errno is set
	if (!(s.st_mode & S_IXUSR)) {
		errno = ENOEXEC;
		return -1;
	}
	void* mem = malloc(s.st_size);
	if (mem == NULL) {
		errno = ENOMEM;
		return -1;
	}
	int rv = read(fd, mem, s.st_size);
	if (rv == -1) return -1; // errno is set
	if (rv != s.st_size) {
		errno = EINVAL; // nicht ganz korrekt, aber ich weiss nicht was ich hier sonst setzen sollte
		return -1;
	}
	return exec(mem, argv, env, __global_fs_state);
}

int execl(const char* path, ...) {
	char** argv = (void*)path + sizeof(char*);
	return execfile(path, argv, environ);
}

int execle(const char * path, ...) {
	char** argv = (void*)path + sizeof(char*);
	va_list args;
	va_start(args, path);
	while (va_arg(args, char*) != NULL);
	return execfile(path, argv, va_arg(args, char**));
}

int execv(const char* path, char* const argv[]) {
	return execfile(path, (char**)argv, environ);
}

int execve(const char* path, char* const argv[], char* const env[]) {
	return execfile(path, (char**)argv, (char**)env);
}

unsigned int sleep(unsigned int seconds)
{
	long uptime;
	struct sysinfo info;

	sysinfo(&info);
	uptime = info.uptime;

	while (1)
	{
		sysinfo(&info);
		if ( (info.uptime - uptime) >= seconds ) return 0;
		pt_yield();
	}
}

pid_t tcgetpgrp(int fd) {
	isatty(fd); // sets errno
	return -1;
}

int tcsetpgrp(int fd, pid_t pgid_id __attribute__((unused))) {
	isatty(fd); // sets errno
	return -1;
}

long sysconf(int name) {
	switch (name) {
		case _SC_ARG_MAX: return 4096;
		case _SC_CLK_TCK: return 180000000;
		case _SC_PAGESIZE: return 4096;
	}
	errno = EINVAL;
	return -1;
}

gid_t getegid(void) {
  return 0;
}

uid_t geteuid(void) {
  return 0;
}

gid_t getgid(void) {
  return 0;
}

uid_t getuid(void) {
  return 0;
}

int getgroups(int gidsetsize, gid_t grouplist[]) {
  return 0;
}

int setgid(gid_t gid){
  if (gid==0) return 0;
  errno=EPERM;
  return -1;
}

int setuid(uid_t uid){
  if (uid==0) return 0;
  errno=EPERM;
  return -1;
}

int setregid(gid_t rgid, gid_t egid) {
  if (rgid==(gid_t)-1 || rgid==0 || egid==(gid_t)-1 || egid==0){
    return 0;
  }
  errno=EPERM;
  return -1;
}

int setreuid(uid_t ruid, uid_t euid) {
  if (ruid<(uid_t)-1 || ruid>0 || euid<(uid_t)-1 || euid>0){
    errno=EPERM;
    return -1;
  }
  return 0;
}

int setegid(gid_t gid) {
  if (gid==0) return 0;
  errno=EPERM;
  return -1;
}

int seteuid(uid_t uid) {
  if (uid==0) return 0;
  errno=EPERM;
  return -1;
}
